﻿using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;

namespace Narwhunderful.Aphelia.Framework.IO.Serialization.Binary
{

    public sealed class BinaryPropertySerializer : PropertySerializer
    {

        private readonly IBinaryWriter _writer;

        public BinaryPropertySerializer(IBinaryWriter writer)
        {
            if (writer == null) throw new ArgumentNullException("writer");
            _writer = writer;
        }

        public override void Open(Stream stream)
        {
            _writer.Open(stream);
        }

        public override void Close()
        {
            _writer.Close();
        }

        private void writePropertyHeader(byte elementId, string name, Type valueType)
        {
            _writer.WriteElementId(elementId);
            _writer.WriteName(name);
            _writer.WriteType(valueType);
        }

        protected override void SerializeNullProperty(PropertyTypeInfo<NullProperty> property)
        {
            writePropertyHeader(Elements.Null, property.Name, property.ValueType);
        }

        protected override void SerializeSimpleProperty(PropertyTypeInfo<SimpleProperty> property)
        {
            writePropertyHeader(Elements.SimpleObject, property.Name, property.ValueType);
            _writer.WriteValue(property.Property.Value);
        }

        protected override void SerializeMultiDimensionalArrayProperty(PropertyTypeInfo<MultiDimensionalArrayProperty> property)
        {
            writePropertyHeader(Elements.MultiArray, property.Name, property.ValueType);

            // ElementType
            _writer.WriteType(property.Property.ElementType);

            // DimensionInfos
            WriteDimensionInfos(property.Property.DimensionInfos);

            // Einträge
            WriteMultiDimensionalArrayItems(property.Property.Items, property.Property.ElementType);
        }

        private void WriteMultiDimensionalArrayItems(IList<MultiDimensionalArrayItem> items, Type defaultItemType)
        {
            // Count
            _writer.WriteNumber(items.Count);

            // Items
            foreach (MultiDimensionalArrayItem item in items)
            {
                WriteMultiDimensionalArrayItem(item, defaultItemType);
            }
        }

        private void WriteMultiDimensionalArrayItem(MultiDimensionalArrayItem item, Type defaultItemType)
        {
            // Write coordinates
            _writer.WriteNumbers(item.Indexes);

            // Write Data
            SerializeCore(new PropertyTypeInfo<Property>(item.Value, defaultItemType));
        }

        private void WriteDimensionInfos(IList<DimensionInfo> dimensionInfos)
        {
            // count
            _writer.WriteNumber(dimensionInfos.Count);

            // items
            foreach (DimensionInfo info in dimensionInfos)
            {
                WriteDimensionInfo(info);
            }
        }

        private void WriteDimensionInfo(DimensionInfo info)
        {
            // Length
            _writer.WriteNumber(info.Length);

            // LowerBound
            _writer.WriteNumber(info.LowerBound);
        }

        protected override void SerializeSingleDimensionalArrayProperty(PropertyTypeInfo<SingleDimensionalArrayProperty> property)
        {
            writePropertyHeader(Elements.SingleArray, property.Name, property.ValueType);

            // ElementType
            _writer.WriteType(property.Property.ElementType);

            // Lower Bound
            _writer.WriteNumber(property.Property.LowerBound);

            // items
            writeItems(property.Property.Items, property.Property.ElementType);
        }

        private void writeItems(ICollection<Property> items, Type defaultItemType)
        {
            // Count
            _writer.WriteNumber(items.Count);

            // items
            foreach (Property item in items)
            {
                SerializeCore(new PropertyTypeInfo<Property>(item, defaultItemType));
            }
        }

        protected override void SerializeDictionaryProperty(PropertyTypeInfo<DictionaryProperty> property)
        {
            writePropertyHeader(Elements.Dictionary, property.Name, property.ValueType);

            // type of keys
            _writer.WriteType(property.Property.KeyType);

            // type of values
            _writer.WriteType(property.Property.ValueType);

            // Properties
            writeProperties(property.Property.Properties, property.Property.Type);

            // Items
            WriteDictionaryItems(property.Property.Items, property.Property.KeyType, property.Property.ValueType);
        }

        private void WriteDictionaryItems(IList<KeyValueItem> items, Type defaultKeyType, Type defaultValueType)
        {
            // count
            _writer.WriteNumber(items.Count);

            foreach (KeyValueItem item in items)
            {
                WriteDictionaryItem(item, defaultKeyType, defaultValueType);
            }
        }

        private void WriteDictionaryItem(KeyValueItem item, Type defaultKeyType, Type defaultValueType)
        {
            // Key
            SerializeCore(new PropertyTypeInfo<Property>(item.Key, defaultKeyType));

            // Value
            SerializeCore(new PropertyTypeInfo<Property>(item.Value, defaultValueType));
        }

        protected override void SerializeCollectionProperty(PropertyTypeInfo<CollectionProperty> property)
        {
            writePropertyHeader(Elements.Collection, property.Name, property.ValueType);

            // ElementType
            _writer.WriteType(property.Property.ElementType);

            // Properties
            writeProperties(property.Property.Properties, property.Property.Type);

            //Items
            writeItems(property.Property.Items, property.Property.ElementType);
        }

        protected override void SerializeComplexReferenceProperty(PropertyTypeInfo<ComplexReferenceProperty> property)
        {
            writePropertyHeader(Elements.ComplexObjectReference, property.Name, property.ValueType);
            _writer.WriteNumber(property.Property.ReferenceTarget.ComplexReferenceId);
        }

        protected override void SerializeComplexProperty(PropertyTypeInfo<ComplexProperty> property)
        {
            if (property.Property.IsReferencedMoreThanOnce)
            {
                writePropertyHeader(Elements.ComplexObjectWithId, property.Name, property.ValueType);
                _writer.WriteNumber(property.Property.ComplexReferenceId);
            }
            else
                writePropertyHeader(Elements.ComplexObject, property.Name, property.ValueType);

            // Properties
            writeProperties(property.Property.Properties, property.Property.Type);
        }

        private void writeProperties(PropertyCollection properties, Type ownerType)
        {
            // How many
            _writer.WriteNumber(Convert.ToInt16(properties.Count));

            // Serialize all of them
            foreach (Property property in properties)
            {
                PropertyInfo propertyInfo = ownerType.GetProperty(property.Name);
                SerializeCore(new PropertyTypeInfo<Property>(property, propertyInfo.PropertyType));
            }
        }
    }
}
